package com.im.tcp.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.ecode.imecode.pack.MessagePack;
import com.ecode.imecode.pack.message.ChartMessageAckPack;
import com.im.tcp.feign.RpcServer;
import com.im.tcp.publisher.MqChartMsgProduce;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constants.Constants;
import com.ecode.imecode.pack.LoginPack;
import com.ecode.imecode.proto.Message;
import com.im.tcp.utils.SessionSocketHolder;
import com.lld.im.common.enums.ConnectStatusEnum;
import com.lld.im.common.enums.commond.GroupEventCommand;
import com.lld.im.common.enums.commond.MessageCommand;
import com.lld.im.common.enums.commond.SystemCommand;
import com.lld.im.common.model.UserClientDto;
import com.lld.im.common.model.UserSession;
import com.lld.im.common.model.message.CheckSendMessageReq;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;

import java.net.InetAddress;
import java.net.UnknownHostException;

@Slf4j
public class NettyMsgServerHandler extends SimpleChannelInboundHandler<Message> {

    private String brokerId;
    private RedissonClient redissonClient;
    private RpcServer rpcServer;

    public NettyMsgServerHandler(String brokerId, RedissonClient redissonClient, RpcServer rpcServer) {
        this.brokerId = brokerId;
        this.redissonClient = redissonClient;
        this.rpcServer = rpcServer;
    }


    //ToDo 保存登录用户信息 也就是保存每一次的channel  redis中保存用户信息 系统保存连接信息
    // 可以使用Map结构 (用户存在多段登录) 以 clientType-sessionInfo
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Message message) throws Exception {
        System.out.println("解析后的数据:" + message);
        // TODO 根据commend判断用户意图
        int command = message.getMessageHeader().getCommand();
        if (command == SystemCommand.LOGIN.getCommand()) {
            doLogin(channelHandlerContext, message);
        } else if (command == SystemCommand.LOGOUT.getCommand()) {
            SessionSocketHolder.removeUserSession(channelHandlerContext.channel());
        } else if (command == SystemCommand.PING.getCommand()) {
            //TODO 心跳检测 记录用户上一次写的时间
            channelHandlerContext.channel().attr(AttributeKey.valueOf(Constants.PING_LAST_TIME)).set(System.currentTimeMillis());
        }else if (command== MessageCommand.MSG_P2P.getCommand()
            ||command== GroupEventCommand.MSG_GROUP.getCommand()){
            //TODO 在这里使用feign前置校验 不通过 直接ack返回
            //用户消息发送
            doMsgBeforeCheck(channelHandlerContext, message, command);
        }
        //TODO

    }

    private void doMsgBeforeCheck(ChannelHandlerContext channelHandlerContext, Message message, int command) {
        JSONObject jsonObject=JSONObject.parseObject(JSON.toJSONString(message.getBodyData()));
        CheckSendMessageReq checkSendMessageReq = new CheckSendMessageReq();
        checkSendMessageReq.setCommand(command);
        checkSendMessageReq.setAppId(Integer.valueOf(message.getMessageHeader().getAppId()));
        checkSendMessageReq.setFromId(jsonObject.getString("fromId"));
        //对于p2p和群 需要分开处理
        if(command == MessageCommand.MSG_P2P.getCommand()){
            String  toId = jsonObject.getString("toId");
            checkSendMessageReq.setToId(toId);
        }else {
            String groupId = jsonObject.getString("groupId");
            checkSendMessageReq.setGroupId(groupId);
        }
        ResponseVO responseVO = rpcServer.checkMessage(checkSendMessageReq);
        if(responseVO.isOk()){
            MqChartMsgProduce.sendMsg(message, command);
        }else {
            int ackCommand;
            if(command == MessageCommand.MSG_P2P.getCommand()){
                ackCommand = MessageCommand.MSG_ACK.getCommand();
            }else {
                ackCommand = GroupEventCommand.GROUP_MSG_ACK.getCommand();
            }
            //TODO 直接错误ack确认 发回客户端
            ChartMessageAckPack chartMessageAckPack = new
                    ChartMessageAckPack(jsonObject.getInteger("messageId"));
            responseVO.setData(chartMessageAckPack);
            MessagePack<ResponseVO> responseVOMessagePack = new MessagePack<>();
            responseVOMessagePack.setData(responseVO);
            responseVOMessagePack.setCommand(ackCommand);
            channelHandlerContext.channel().writeAndFlush(responseVOMessagePack);
        }
    }

    private void doLogin(ChannelHandlerContext channelHandlerContext, Message message) {
        LoginPack loginPack = JSONObject.parseObject(message.getBodyData().toString(),
                new TypeReference<LoginPack>() {
                }.getType());
        UserSession userSession = createUserSessionByChannel(channelHandlerContext, message, loginPack);
        setUserSessionServerInfo(userSession);
        //TODO 保存在redis中 使用 appId:userSession:userId 作为key 以clientType为hashKey userSessionDAO为value
        saveUserSessionInRedis(userSession);
        //发送用户登录的广播
        sendLoginMsg(message, loginPack.getUserId());
    }

    private void sendLoginMsg(Message message, String userId) {
        RTopic topic = redissonClient.getTopic(Constants.RedissonConfig.LOGIN_USER_CHANNEL);
        UserClientDto userClientDto = new UserClientDto(message.getMessageHeader().getAppId(),
                message.getMessageHeader().getClientType(), userId, message.getImei());
        topic.publish(JSONObject.toJSONString(userClientDto));
    }

    private UserSession createUserSessionByChannel(ChannelHandlerContext channelHandlerContext, Message message
            , LoginPack loginPack) {
        // TODO 讲用户信息保存在channel中
        NioSocketChannel channel = (NioSocketChannel) channelHandlerContext.channel();
        channel.attr(AttributeKey.valueOf(Constants.USERID)).set(loginPack.getUserId());
        channel.attr(AttributeKey.valueOf(Constants.APPID)).set(message.getMessageHeader().getAppId());
        channel.attr(AttributeKey.valueOf(Constants.IMEI)).set(message.getImei());
        channel.attr(AttributeKey.valueOf(Constants.CLIENT_TYPE))
                .set(message.getMessageHeader().getClientType());
        UserSession userSession = new UserSession(message.getMessageHeader().getAppId() + ""
                , loginPack.getUserId(), message.getMessageHeader().getClientType());
        userSession.setConnectState(ConnectStatusEnum.CONNECTING.getCode());
        userSession.setImei(message.getImei());
        userSession.setVersion(message.getMessageHeader().getVersion());
        // TODO 保存用户各个客户端信息在系统内存中
        SessionSocketHolder.put(userSession, channel);
        return userSession;
    }

    /**
     * 将用户信息保存在redis中 userSession中客户端与服务端的信息 在后期 将作为路由表信息 分发给指定的服务器
     */
    private void saveUserSessionInRedis(UserSession userSession) {
        RMap<String, String> map = redissonClient.getMap(userSession.getAppId() +
                Constants.RedissonConfig.USER_SESSION_KEY +
                userSession.getUserId());
        String hashKey= userSession.getClientType().toString() + ":" + userSession.getImei();
        //这里的key 以clientType:imei 来保证唯一性
        map.put(hashKey, JSONObject.toJSONString(userSession));
    }

    /**
     * 将服务端信息添加到userSession
     *
     * @param userSession
     */
    private void setUserSessionServerInfo(UserSession userSession) {
        userSession.setBrokerId(this.brokerId);
        try {
            String hostName = InetAddress.getLocalHost().getHostName();
            userSession.setBrokerHost(hostName);
        } catch (UnknownHostException e) {
            log.error("setUserSessionHost 失败！{}", e.getMessage());
        }
    }


}
